package com.example.framework.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.spring.starter.RedissonAutoConfigurationV2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.time.Duration;

/**
 * @author yuzhenxin
 * @date 2023-05-28 22:17
 */
@AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的：使用自己定义的 RedisTemplate Bean
@EnableCaching
public class RedisAutoConfiguration {

    @Value("${redis.cacheable.duration-seconds:600}")
    private long durationSeconds;

    /**
     * 默认是JDK序列化，key和value前面都多了一串特殊字符，需要实现自定义序列化方式
     *
     * @param redisConnectionFactory 连接工厂
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // key序列化方式
        template.setKeySerializer(RedisSerializer.string());
        // value序列化方式
        template.setValueSerializer(RedisSerializer.string());
        // hash-key序列化方式
        template.setHashKeySerializer(RedisSerializer.string());
        // hash-value序列化
        template.setHashValueSerializer(RedisSerializer.string());
        // 设置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // 设置CacheManager缓存规则，配置序列化（解决乱码的问题）
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 过期时间600秒
                .entryTtl(Duration.ofSeconds(this.durationSeconds))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer()))
                // 禁用缓存空值
                .disableCachingNullValues();
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }

    /**
     * 自定义json序列化
     *
     * @return json序列化
     */
    private RedisSerializer<Object> jsonSerializer() {
        // 使用这个序列化效率较高，但无法直接强转对象，需要转化成Json存储，获取的时候再通过Json反序列化成对象
        // 解决查询缓存转换异常的问题，底层通过ObjectMapper完成对象序列化和反序列化
        ObjectMapper objectMapper = new ObjectMapper();
        // 指定要序列化的域，field,get和set,以及修饰符范围，ANY是都有包括private和public
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 对象属性值为null时,不进行序列化存储
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 假如没有这个配置,redis存储数据时不存储类型,反序列化时会默认将其数据存储到map
        objectMapper.activateDefaultTyping(
                //多态校验分析
                objectMapper.getPolymorphicTypeValidator(),
                // 指定序列化输入的类型，类不能使用final修饰
                ObjectMapper.DefaultTyping.NON_FINAL,
                // PROPERTY 表示类型会以json对象属性形式存储
                JsonTypeInfo.As.PROPERTY);

        // 使用这个序列化的时候会存储对象的全限定名，可以直接强转对象
//        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
    }
}
